package com.linden.std.service.auth.impl;

import com.alibaba.fastjson.JSON;
import com.linden.std.Utils.UserContextUtil;
import com.linden.std.constant.CommonConstant;
import com.linden.std.domain.dto.ChangePasswordRequest;
import com.linden.std.domain.dto.LoginRequest;
import com.linden.std.domain.po.SysLoginLog;
import com.linden.std.domain.po.SysRole;
import com.linden.std.domain.po.SysUser;
import com.linden.std.domain.vo.LoginResponse;
import com.linden.std.domain.vo.TokenResponse;
import com.linden.std.domain.vo.UserVO;
import com.linden.std.enums.ExceptionEnums;
import com.linden.std.exception.CustomException;
import com.linden.std.service.ISysLoginLogService;
import com.linden.std.service.ISysUserService;
import com.linden.std.service.auth.LocalAuthService;
import com.linden.std.service.auth.LoginSecurityService;
import com.linden.std.service.auth.TokenService;
import com.linden.std.service.session.SessionService;
import com.linden.std.util.RedisTemplateUtil;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 本地认证服务实现
 */
@Slf4j
@Service
public class LocalAuthServiceImpl implements LocalAuthService {
    
    @Autowired
    private ISysUserService userService;
    @Autowired
    private TokenService tokenService;
    @Autowired
    private ISysLoginLogService loginLogService;
    @Autowired
    private SessionService sessionService;
    @Autowired
    private RedisTemplateUtil redisTemplateUtil;
    @Autowired
    private ApplicationContext applicationContext;
    @Autowired
    private LoginSecurityService loginSecurityService;
    
    @Value("${std.security.token.access-timeout:7200}")
    private Integer tokenTimeout;
    
    private PasswordEncoder getPasswordEncoder() {
        return applicationContext.getBean(PasswordEncoder.class);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public LoginResponse login(LoginRequest request, HttpServletRequest httpRequest) {
        // 验证登录请求参数
        validateLoginRequest(request);
        
        // 获取客户端IP
        String clientIp = getClientIp(httpRequest);
        
        // 检查账户和IP的安全状态
        checkLoginSecurity(request.getUsername(), clientIp);
        
        try {
            // 验证用户名密码
            SysUser user = authenticateUser(request.getUsername(), request.getPassword());
            
            // 检查用户状态
            checkUserStatus(user);
            
            // 登录成功，清除失败记录
            loginSecurityService.clearLoginFailures(request.getUsername(), clientIp);

            // 清理用户之前的所有登录缓存
            clearUserPreviousCache(user.getId());
            
            // 生成令牌
            String accessToken = tokenService.generateAccessToken(user);
            String refreshToken = tokenService.generateRefreshToken(user);
            
            // 构建用户信息
            UserVO userVO = buildUserVO(user);
            
            // 缓存用户信息
            cacheUserInfo(accessToken, userVO);
            
            // 记录登录日志
            recordLoginLog(user, httpRequest, "登录成功");
            
            // 更新最后登录信息
            userService.updateLastLoginInfo(user.getId(), clientIp);
            
            return LoginResponse.builder()
                .accessToken(accessToken)
                .refreshToken(refreshToken)
                .tokenType("Bearer")
                .expiresIn(tokenTimeout)
                .userInfo(userVO)
                .build();
                
        } catch (CustomException e) {
            // 登录失败，记录失败次数
            if (e.getCode().equals(ExceptionEnums.PASSWORD_ERROR.getCode()) || 
                e.getCode().equals(ExceptionEnums.USER_NOT_FOUND.getCode())) {
                loginSecurityService.recordLoginFailure(request.getUsername(), clientIp);
            }
            throw e;
        }
    }

    @Override
    public TokenResponse refreshToken(String refreshToken) {
        if (!tokenService.validateToken(refreshToken)) {
            throw new CustomException(ExceptionEnums.TOKEN_INVALID);
        }
        
        Claims claims = tokenService.parseToken(refreshToken);
        String tokenType = claims.get("tokenType", String.class);
        
        if (!"refresh".equals(tokenType)) {
            throw new CustomException(ExceptionEnums.TOKEN_INVALID);
        }
        
        Long userId = claims.get("userId", Long.class);
        SysUser user = userService.getById(userId);
        
        if (user == null || !CommonConstant.USER_STATUS_ENABLED.equals(user.getStatus())) {
            throw new CustomException(ExceptionEnums.USER_NOT_FOUND);
        }
        
        String newAccessToken = tokenService.generateAccessToken(user);
        String newRefreshToken = tokenService.generateRefreshToken(user);
        
        UserVO userVO = buildUserVO(user);
        cacheUserInfo(newAccessToken, userVO);
        
        return TokenResponse.builder()
            .accessToken(newAccessToken)
            .refreshToken(newRefreshToken)
            .tokenType("Bearer")
            .expiresIn(tokenTimeout)
            .build();
    }

    @Override
    public void logout(String token, HttpServletRequest request) {
        if (StringUtils.isBlank(token)) {
            return;
        }
        
        try {
            Claims claims = tokenService.parseToken(token);
            if (claims != null) {
                long expirationTime = claims.getExpiration().getTime() / 1000;
                tokenService.addToBlacklist(token, expirationTime);
                
                // 获取用户ID并清理分离存储的缓存
                Long userId = claims.get("userId", Long.class);
                if (userId != null) {
                    sessionService.clearUserCache(userId);
                }
            }
            
            String cacheKey = CommonConstant.REDIS_LOGIN_USER_KEY_PREFIX + token;
            redisTemplateUtil.delete(cacheKey);
            
            log.info("用户登出成功");
        } catch (Exception e) {
            log.warn("登出处理异常: {}", e.getMessage());
        }
    }

    @Override
    public UserVO validateToken(String token) {
        if (StringUtils.isBlank(token)) {
            return null;
        }
        
        // 先从缓存获取用户信息
        UserVO userVO = getUserFromCache(token);
        if (userVO != null) {
            return userVO;
        }
        
        // 验证Token有效性
        if (!tokenService.validateToken(token)) {
            return null;
        }
        
        // 解析Token获取用户信息
        Claims claims = tokenService.parseToken(token);
        Long userId = claims.get("userId", Long.class);
        
        SysUser user = userService.getById(userId);
        if (user == null || !CommonConstant.USER_STATUS_ENABLED.equals(user.getStatus())) {
            return null;
        }
        
        // 重新构建用户信息并缓存
        userVO = buildUserVO(user);
        cacheUserInfo(token, userVO);
        
        return userVO;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void changePassword(ChangePasswordRequest request) {
        UserVO currentUser = UserContextUtil.getUser();
        if (currentUser == null) {
            throw new CustomException(ExceptionEnums.UNAUTHORIZED);
        }
        
        SysUser user = userService.getById(currentUser.getUserId());
        if (user == null) {
            throw new CustomException(ExceptionEnums.USER_NOT_FOUND);
        }
        
        // 验证原密码
        if (!getPasswordEncoder().matches(request.getOldPassword(), user.getPassword())) {
            throw new CustomException(ExceptionEnums.OLD_PASSWORD_ERROR);
        }
        
        // 验证两次密码一致性
        if (!request.getNewPassword().equals(request.getConfirmPassword())) {
            throw new CustomException(ExceptionEnums.PASSWORD_NOT_MATCH);
        }
        
        // 更新密码
        user.setPassword(getPasswordEncoder().encode(request.getNewPassword()));
        userService.updateById(user);
        
        log.info("用户{}修改密码成功", user.getUsername());
    }

    private void validateLoginRequest(LoginRequest request) {
        if (StringUtils.isBlank(request.getUsername())) {
            throw new CustomException(ExceptionEnums.USER_NOT_FOUND);
        }
        if (StringUtils.isBlank(request.getPassword())) {
            throw new CustomException(ExceptionEnums.PASSWORD_ERROR);
        }
    }

    private SysUser authenticateUser(String username, String password) {
        // 根据用户名查询用户
        SysUser user = userService.getUserByUsername(username);
        if (user == null) {
            throw new CustomException(ExceptionEnums.USER_NOT_FOUND);
        }
        
        // 验证密码
        if (!getPasswordEncoder().matches(password, user.getPassword())) {
            recordLoginLog(user, null, "密码错误", 0);
            throw new CustomException(ExceptionEnums.PASSWORD_ERROR);
        }
        
        return user;
    }

    private void checkUserStatus(SysUser user) {
        if (!CommonConstant.USER_STATUS_ENABLED.equals(user.getStatus())) {
            throw new CustomException(ExceptionEnums.USER_DISABLED);
        }
    }
    
    /**
     * 检查登录安全状态
     */
    private void checkLoginSecurity(String account, String ip) {
        // 检查账户是否被锁定
        if (loginSecurityService.isAccountLocked(account)) {
            long remainingTime = loginSecurityService.getAccountLockRemainingTime(account);
            String message = String.format("账户已被锁定，请在 %d 分钟后重试", remainingTime / 60);
            throw new CustomException(ExceptionEnums.ACCOUNT_LOCKED.getCode(), message);
        }
        
        // 检查IP是否被锁定
        if (loginSecurityService.isIpLocked(ip)) {
            long remainingTime = loginSecurityService.getIpLockRemainingTime(ip);
            String message = String.format("您的IP地址已被锁定，请在 %d 分钟后重试", remainingTime / 60);
            throw new CustomException(ExceptionEnums.IP_LOCKED.getCode(), message);
        }
    }

    private UserVO buildUserVO(SysUser user) {
        UserVO userVO = new UserVO();
        userVO.setUserId(user.getId());
        userVO.setAccount(user.getUsername());
        userVO.setEmpNo(user.getEmpNo());
        userVO.setNickName(user.getRealName());
        userVO.setRealName(user.getRealName());
        userVO.setPhone(user.getPhone());
        userVO.setEmail(user.getEmail());
        userVO.setAvatar(user.getAvatar());
        userVO.setStatus(user.getStatus());
        
        // 转换LocalDateTime为Date
        if (user.getLastLoginTime() != null) {
            userVO.setLastLoginTime(Date.from(user.getLastLoginTime().atZone(ZoneId.systemDefault()).toInstant()));
        }
        userVO.setLastLoginIp(user.getLastLoginIp());
        
        // 获取用户权限
        List<String> permissions = userService.obtainUserRoleAndPermissions(user.getId());
        userVO.setPermCodes(permissions != null ? permissions : Collections.emptyList());
        
        // 获取用户角色
        List<SysRole> roles = userService.getUserRoles(user.getId());
        List<String> roleCodes = roles != null ? roles.stream()
            .map(SysRole::getRoleCode)
            .collect(Collectors.toList()) : Collections.emptyList();
        userVO.setRoles(roleCodes);
        
        // 分离存储用户基本信息和权限信息到Redis
        sessionService.cacheUserInfo(user.getId(), userVO);
        sessionService.cacheUserPermissions(user.getId(), permissions != null ? permissions : Collections.emptyList());
        
        return userVO;
    }

    private void cacheUserInfo(String token, UserVO userVO) {
        String cacheKey = CommonConstant.REDIS_LOGIN_USER_KEY_PREFIX + token;
        redisTemplateUtil.set(cacheKey, JSON.toJSONString(userVO), tokenTimeout);
    }

    private UserVO getUserFromCache(String token) {
        String cacheKey = CommonConstant.REDIS_LOGIN_USER_KEY_PREFIX + token;
        String userInfoJson = redisTemplateUtil.get(cacheKey);
        
        if (StringUtils.isNotBlank(userInfoJson)) {
            try {
                return JSON.parseObject(userInfoJson, UserVO.class);
            } catch (Exception e) {
                log.warn("解析缓存用户信息失败: {}", e.getMessage());
                redisTemplateUtil.delete(cacheKey);
            }
        }
        
        return null;
    }

    /**
     * 清理用户之前的所有登录缓存
     */
    private void clearUserPreviousCache(Long userId) {
        try {
            String userTokenKey = CommonConstant.REDIS_LOGIN_USER_KEY_PREFIX + "tokens:" + userId;
            String tokensJson = redisTemplateUtil.get(userTokenKey);

            if (StringUtils.isNotBlank(tokensJson)) {
                List<String> tokens = JSON.parseArray(tokensJson, String.class);
                if (tokens != null && !tokens.isEmpty()) {
                    for (String token : tokens) {
                        String cacheKey = CommonConstant.REDIS_LOGIN_USER_KEY_PREFIX + token;
                        redisTemplateUtil.delete(cacheKey);
                        // 将旧token加入黑名单
                        tokenService.addToBlacklist(token, System.currentTimeMillis() / 1000 + tokenTimeout);
                    }
                }
            }

            // 清理用户token记录
            redisTemplateUtil.delete(userTokenKey);

            log.info("清理用户{}的历史登录缓存", userId);
        } catch (Exception e) {
            log.warn("清理用户{}历史缓存失败: {}", userId, e.getMessage());
        }
    }

    private String getClientIp(HttpServletRequest request) {
        if (request == null) {
            return "unknown";
        }
        String ip = request.getHeader("X-Forwarded-For");
        if (StringUtils.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("X-Real-IP");
        }
        if (StringUtils.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }

    private void recordLoginLog(SysUser user, HttpServletRequest request, String msg) {
        recordLoginLog(user, request, msg, 1);
    }

    private void recordLoginLog(SysUser user, HttpServletRequest request, String msg, Integer status) {
        try {
            SysLoginLog loginLog = new SysLoginLog();
            loginLog.setUserId(user.getId());
            loginLog.setUsername(user.getUsername());
            loginLog.setLoginIp(getClientIp(request));
            loginLog.setLoginLocation(getLoginLocation(getClientIp(request)));
            loginLog.setBrowser(getBrowserInfo(request));
            loginLog.setOs(getOsInfo(request));
            loginLog.setStatus(status); // 1:成功, 0:失败
            loginLog.setMsg(msg);
            loginLog.setLoginTime(LocalDateTime.now());

            // 异步保存登录日志，避免影响登录性能
            loginLogService.save(loginLog);

            log.info("记录用户{}登录日志: {} - 状态: {}", user.getUsername(), msg, status == 1 ? "成功" : "失败");
        } catch (Exception e) {
            log.error("保存登录日志失败: {}", e.getMessage(), e);
        }
    }

    private String getBrowserInfo(HttpServletRequest request) {
        if (request == null) {
            return "未知浏览器";
        }
        String userAgent = request.getHeader("User-Agent");
        if (StringUtils.isBlank(userAgent)) {
            return "未知浏览器";
        }

        if (userAgent.contains("Chrome")) {
            return "Chrome";
        } else if (userAgent.contains("Firefox")) {
            return "Firefox";
        } else if (userAgent.contains("Safari")) {
            return "Safari";
        } else if (userAgent.contains("Edge")) {
            return "Edge";
        } else {
            return "其他浏览器";
        }
    }

    private String getOsInfo(HttpServletRequest request) {
        if (request == null) {
            return "未知系统";
        }
        String userAgent = request.getHeader("User-Agent");
        if (StringUtils.isBlank(userAgent)) {
            return "未知系统";
        }

        if (userAgent.contains("Windows")) {
            return "Windows";
        } else if (userAgent.contains("Mac")) {
            return "Mac OS";
        } else if (userAgent.contains("Linux")) {
            return "Linux";
        } else if (userAgent.contains("Android")) {
            return "Android";
        } else if (userAgent.contains("iPhone") || userAgent.contains("iOS")) {
            return "iOS";
        } else {
            return "其他系统";
        }
    }

    private String getLoginLocation(String ip) {
        if (StringUtils.isBlank(ip) || "unknown".equals(ip) || "127.0.0.1".equals(ip) || "0:0:0:0:0:0:0:1".equals(ip)) {
            return "本地登录";
        }
        // 这里可以集成IP地址库来获取真实地理位置
        // 暂时返回简单描述
        return "外网登录";
    }
}